﻿// *---------------------------------------------------------------------------------------------------------*
// Solution:         JwikIDE									
// Project:           JwiKIDE								
// Source File:    VMFile.cs									
// *---------------------------------------------------------------------------------------------------------*
// 
// © 2012 Digital Six Labs, LLC All Rights Reserved
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
// and associated documentation files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or substantial 
// portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// 
// 
// 
// 
namespace JwiKIDE.JavaClassFileClasses
{
    using System;
    using System.CodeDom.Compiler;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;

    using JwiKIDE.Utilities;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public static class VMFile
    {
        const UInt32 Magic = 0xBE000000;

        private const byte Version = 2;

        private static byte[] outputBuffer;

        private static int cur;

        private static UInt32 features;

        public static void Generate(string outFile)
        {
            outputBuffer = new byte[ConfigFile.MaxSize];
            cur = 0;
            WriteHeader();
            WriteClassHeaders();
            WriteConstantEntries();
            WriteStrings();
            WriteMethods();
            UpdateHeader();

            // at this point, output buffer should have a binary image of the VMFile in BigEndian format.
            using(FileStream fs = new FileStream(outFile,FileMode.Create))
            {
                BinaryWriter bw = new BinaryWriter(fs);
                
                for(int i=0;i<cur;i++)
                    bw.Write(outputBuffer[i]);

            }
            string headerFile = outFile.Split(new[] { '.' })[0] + ".h";
            using(StreamWriter sw = new StreamWriter(headerFile))
            {
                sw.WriteLine("// Autogenerated by VMClassTool");
                sw.WriteLine("// "+DateTime.Now.ToLongDateString());
                sw.WriteLine("// ");
                sw.WriteLine("// "+outFile);
                sw.WriteLine("{");

                sw.Write("\t");
                int count = 0;
                for (int i = 0; i < cur;i++ )
                {
                    sw.Write("0x{0:x2}",outputBuffer[i]); 
                    if(i<(cur-1))
                        sw.Write(",");
                    count++;
                    if(count==12)
                    {
                        count = 0;
                        sw.WriteLine();
                        sw.Write("\t");
                    }
                }
                sw.WriteLine();
                sw.WriteLine("}");
            }
        }
        private static void Write8(byte b)
        {
            outputBuffer[cur++] = b;
        }
        private static void Write16(UInt16 v)
        {
            Write8((byte)((v>>8)&0xff));
            Write8((byte)(v&0xff));
        }
        private static void Write32(UInt32 v)
        {
            Write8((byte)((v >> 24) & 0xff));
            Write8((byte)((v >> 16) & 0xff));
            Write8((byte)((v >> 8) & 0xff));
            Write8((byte)(v & 0xff));
        }

        private static void UpdateHeader()
        {
            var oldCur = cur;
            cur = 0;
            WriteHeader();
            cur = oldCur;
        }

        private static void WriteMethods()
        {
            int codeOffset = 0;

            // build the method id table
            MethodIdTable.Build();

            // write all Method headers
            for (int i = 0; i < ClassLoader.TotalMethodCount; i++)
            {
                MethodInfo methodInfo = ClassLoader.GetMethod(i);

                // offset from this header to bytecode (this header is 8 bytes
                // in size)
                Write16((ushort)((ClassLoader.TotalMethodCount - i) * 8 + codeOffset)); // code_index 
                Write16((UInt16)((ClassLoader.GetClassIndex(i) << 8) + MethodIdTable.Indexes[i])); // id
                Write8((byte)((methodInfo.Name == "<clinit>") ? 1 : 0)); // flags
                Write8((byte)methodInfo.GetArgs()); // args
                Write8((byte)methodInfo.CodeInfo.MaxLocals); // max_locals
                Write8((byte)methodInfo.CodeInfo.MaxStack); // max_stack

                codeOffset += methodInfo.CodeInfo.ByteCode.Length;
            }

            // write bytecode
            for (int i = 0; i < ClassLoader.TotalMethodCount; i++)
            {
                ClassInfo classInfo = ClassLoader.GetClassInfoFromMethodIndex(i); //  getClassInfoFromMethodIndex(i);
                MethodInfo methodInfo = ClassLoader.GetMethod(i); //getMethod(i);


                byte[] code = ClassLoader.GetMethod(i).CodeInfo.ByteCode;

                // adjust references etc
                CodeTranslator.Translate(classInfo, code);

                // and write bytecode
                for (int j = 0; j < code.Length; j++) Write8(code[j]);
            }
        }

        private static void WriteStrings()
        {
            // write array of string offsets
            int offset = 2 * ClassLoader.TotalStringCount;
            for(int i=0;i<ClassLoader.TotalStringCount;i++) 
            {
                Write16((UInt16)offset);
                offset += ClassLoader.GetString(i).Length+1; //getString(i).length()+1;
            }

            // write the strings itself
            for(int i=0;i<ClassLoader.TotalStringCount;i++) 
            {
                String str = ClassLoader.GetString(i);

                // write zero terminated c strings
                for(int j=0;j<str.Length;j++) 
                    Write8((byte)str[j]);
                Write8(0);
            }
        }

        private static void WriteConstantEntries()
        {
            for(int i=0;i<ClassLoader.TotalConstantCount;i++)
            {

                Write32((uint)ClassLoader.GetConstantEntry(i));//.getConstantEntry(i));
            }
        }

        private static void WriteClassHeaders()
        {
            for (int i = 0; i < ClassLoader.Classes.Count; i++)
            {
                ClassInfo classInfo = ClassLoader.Classes[i];

                Write8((byte)classInfo.GetSuperClassIndex());
                Write8((byte)classInfo.TotalNonStaticFields);
            }
        }

        private static void WriteHeader()
        {
            UInt16 offset = 15;    // header size: 15 bytes

            Write32(Magic | features);
            Write8(Version);
            if(ClassLoader.TotalMethodCount>255)
                throw new IOException("Too many methods.  Only 255 are allowed.");
            Write8((byte)ClassLoader.TotalMethodCount);
            short mainIndex = ClassLoader.Classes[0].GetMethodIndex("main", "([Ljava/lang/String;)V");
            if(mainIndex==-1)
                throw new IOException("Main() not found.");
            Write16((ushort)mainIndex);

            // offset to constant data
            offset += (UInt16)(2 * ClassLoader.Classes.Count); // class header size: 2bytes
            Write16(offset);

            // offset to string data
            offset += (UInt16)(4 * ClassLoader.TotalConstantCount); // constant value size: 4bytes
            Write16(offset);

            // offset to method data
            offset += (UInt16)(2 * ClassLoader.TotalStringCount); // string indices
            offset += ClassLoader.TotalStringSize;  // string data
            Write16(offset);
            if(ClassLoader.TotalStaticFields>255)
                throw new IOException("Too many static fields.  Only 255 are allowed.");
            Write8((byte)ClassLoader.TotalStaticFields);  // static fields
        }


    }
}
